package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.product.mapper.*;
import com.atguigu.gmall.product.model.*;
import com.atguigu.gmall.product.service.SkuManageService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: atguigu
 * @create: 2022-12-26 11:52
 */
@Slf4j
@Service
@SuppressWarnings("all")
public class SkuManageServiceImpl implements SkuManageService {

    @Autowired
    private SkuInfoMapper skuInfoMapper;

    @Autowired
    private SkuImageMapper skuImageMapper;

    @Autowired
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;

    @Autowired
    private SkuAttrValueMapper skuAttrValueMapper;

    @Autowired
    private BaseAttrInfoMapper baseAttrInfoMapper;

    @Autowired
    private SpuSaleAttrMapper spuSaleAttrMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 保存SKU信息
     * 1.将SKU基本信息存入sku_info表中
     * 2.将提交SKU图片存入sku_image表 关联SKU  设置sku_id逻辑外键
     * 3.将提交的平台属性列表 批量保存 sku_attr_value  关联SKU  设置sku_id逻辑外键
     * 4.将提交的销售属性列表 批量保存 sku_sale_attr_value  关联SKU  设置sku_id逻辑外键
     *
     * @param skuInfo SKU相关信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSkuInfo(SkuInfo skuInfo) {
        //1.将SKU基本信息存入sku_info表中
        skuInfoMapper.insert(skuInfo);
        //2.将提交SKU图片存入sku_image表 关联SKU  设置sku_id逻辑外键
        if (!CollectionUtils.isEmpty(skuInfo.getSkuImageList())) {
            skuInfo.getSkuImageList().stream().forEach(skuImage -> {
                //2.1 关联商品SKU 设置逻辑外键sku_Id
                skuImage.setSkuId(skuInfo.getId());
                //2.2 执行批量保存
                skuImageMapper.insert(skuImage);
            });
        }
        //3.将提交的平台属性列表 批量保存 sku_attr_value  关联SKU  设置sku_id逻辑外键
        if (!CollectionUtils.isEmpty(skuInfo.getSkuAttrValueList())) {
            skuInfo.getSkuAttrValueList().stream().forEach(skuAttrValue -> {
                //3.1 关联商品SKU 设置逻辑外键sku_Id
                skuAttrValue.setSkuId(skuInfo.getId());
                //3.2 保存商品SKU平台属性值
                skuAttrValueMapper.insert(skuAttrValue);
            });

        }
        //4.将提交的销售属性列表 批量保存 sku_sale_attr_value  关联SKU  设置sku_id逻辑外键
        if (!CollectionUtils.isEmpty(skuInfo.getSkuSaleAttrValueList())) {
            skuInfo.getSkuSaleAttrValueList().stream().forEach(skuSaleAttrValue -> {
                //4.1 关联商品SPU 设置逻辑外键spu_Id
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());

                //4.2 关联商品SKU 设置逻辑外键sku_Id
                skuSaleAttrValue.setSkuId(skuInfo.getId());
                //4.3 完成保存
                skuSaleAttrValueMapper.insert(skuSaleAttrValue);
            });
        }

        //将新增的商品SKUID存入布隆过滤器
        //5. 获取布隆过滤器，将新增skuID存入布隆过滤器
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(skuInfo.getId());

    }


    /**
     * 根据分类ID查询商品SKU列表
     *
     * @param skuInfoPage
     * @param skuInfo     查询条件
     * @return
     */
    @Override
    public IPage getSkuInfoPage(Page<SkuInfo> skuInfoPage, SkuInfo skuInfo) {
        //1.构建查询条件
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        if (skuInfo.getCategory3Id() != null) {
            queryWrapper.eq(SkuInfo::getCategory3Id, skuInfo.getCategory3Id());
        }
        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        //2.执行查询
        return skuInfoMapper.selectPage(skuInfoPage, queryWrapper);
    }

    /**
     * 将指定SKU进行上架
     * 基于MQ异步更新索引库的上下架状态
     *
     * @param skuId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void onSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoMapper.updateById(skuInfo);

        //发送商品上架消息到Rabbitmq
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);
    }


    /**
     * 将指定SKU进行下架
     *
     * @param skuId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);

        LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
        //设置更新字段
        updateWrapper.set(SkuInfo::getIsSale, 0);
        //设置更新条件
        updateWrapper.eq(SkuInfo::getId, skuId);
        skuInfoMapper.update(skuInfo, updateWrapper);

        //发送商品下架消息到Rabbitmq
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_LOWER, skuId);
    }

    /**
     * 根据SkuID查询SKU商品信息包含图片列表
     *
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = "sku:", suffix = ":info")
    public SkuInfo getSkuInfo(Long skuId) {
        log.info("执行业务方法getSkuInfo");
        //改造前从数据库直接查询
        SkuInfo skuInfo = getSkuInfoDB(skuId);
        //改造后：缓存+分布式锁（RedisTemplate方式）
        //SkuInfo skuInfo = getSkuInfoRedis(skuId);

        //改造后：缓存+分布式锁（Redisson方式）
        //SkuInfo skuInfo = getSkuInfoRedisson(skuId);
        return skuInfo;
    }


    /**
     * 采用Redisson提供实现分布锁
     * 优先从缓存中获取商品信息，缓存未命中，避免出现缓存击穿，分布式锁
     *
     * @param skuId
     * @return
     */
    private SkuInfo getSkuInfoRedisson(Long skuId) {
        try {
            //1.优先从缓存中获取数据，如果命中缓存则直接返回，未命中-采用分布式锁避免缓存击穿
            //1.1 构建商品商品信息Key 形式：sku:29:info
            String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 从缓存中获取商品信息 结果 命中缓存：直接返回  未命中 执行第二步
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
            if (skuInfo == null) {
                //2.尝试获取锁  获取锁成功-执行数据库查询，有值：将查询结果放入缓存 没值：缓存空对象（暂存）
                //2.1 为每个商品声明锁的Key 形式：sku:29:lock
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
                //2.2创建可重入锁对象
                RLock lock = redissonClient.getLock(lockKey);

                //2.3尝试获取锁 结果  获取锁成功：执行业务（查询数据库 有值：放入缓存 没值：缓存空对象） 释放锁
                boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                //2.3.1 获取锁成功 查库缓存 释放锁
                if (flag) {
                    try {
                        //2.3.2 查询数据库
                        skuInfo = this.getSkuInfoDB(skuId);
                        //2.3.2.1 没值 缓存空对象
                        if (skuInfo == null) {
                            redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                            return skuInfo;
                        }
                        //2.3.2.2 有值：将数据放入缓存
                        redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        //2.3.3 释放锁
                        lock.unlock();
                    }
                    return skuInfo;
                } else {
                    //2.2.2 获取锁失败 自旋下次重试
                    Thread.sleep(200);
                    return this.getSkuInfoRedisson(skuId);
                }
            } else {
                //命中缓存则直接返回
                return skuInfo;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //兜底方案：查询数据库
        return this.getSkuInfoDB(skuId);
    }


    /**
     * 采用SpringDataRedis提供实现分布锁
     * 优先从缓存中获取商品信息，缓存未命中，避免出现缓存击穿，分布式锁
     *
     * @param skuId
     * @return
     */
    private SkuInfo getSkuInfoRedis(Long skuId) {
        try {
            //1.优先从缓存中获取数据，如果命中缓存则直接返回，未命中-采用分布式锁避免缓存击穿
            //1.1 构建商品详情key-缓存商品信息Key 形式： sku:29:info
            String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;

            //1.2 根据key查询缓存中商品数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
            if (skuInfo == null) {
                //2.尝试获取锁  获取锁成功-执行数据库查询，有值：将查询结果放入缓存 没值：缓存空对象（暂存）
                //2.1 为每个查询商品构建商品SKU锁Key值 形式：sku:29:lock  sku:30:lock
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
                //2.2 构建锁的值UUID
                String uuid = UUID.randomUUID().toString().replace("-", "");
                //2.3 调用redis执行set key val ex 10 nx 获取锁
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
                //2.3.1 获取锁成功，执行业务（查询数据库，放入缓存）
                if (flag) {
                    //2.4 执行业务：查询数据库获取商品信息
                    skuInfo = this.getSkuInfoDB(skuId);
                    //2.4.1 数据库中本身不存在 短时间缓存空对象,返回空对象
                    if (skuInfo == null) {
                        redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    }
                    //2.4.2 数据库中有数据，将数据加入缓存
                    redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);

                    //2.5 将锁释放掉 保证删除原子性采用lua脚本
                    String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setScriptText(script);
                    redisScript.setResultType(Long.class);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), uuid);
                    return skuInfo;
                } else {
                    //2.3.2 获取锁失败，自旋等待下次获取
                    Thread.sleep(100);
                    return this.getSkuInfoRedis(skuId);
                }
            } else {
                //命中缓存直接返回即可
                return skuInfo;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //兜底方案：查询数据库
        return getSkuInfoDB(skuId);
    }


    /**
     * 直接查询数据库获取商品数据
     *
     * @param skuId
     * @return
     */
    //@GmallCache
    private SkuInfo getSkuInfoDB(Long skuId) {
        //1.根据商品SkuID（主键）查询sku_info表得到商品信息
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        //2.根据skuId查询商品图片表sku_image得到商品图片列表
        if (skuInfo != null) {
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> skuImages = skuImageMapper.selectList(queryWrapper);
            //封装商品图片列表
            skuInfo.setSkuImageList(skuImages);
            return skuInfo;
        }
        return null;
    }

    /**
     * 根据商品实时最新价格
     *
     * @param skuId 商品ID
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        //1.避免出现缓存击穿
        //1.1 构建锁的key
        String lockKey = "sku:price:" + skuId + ":lock";
        //1.2 创建锁对象
        RLock lock = redissonClient.getLock(lockKey);
        try {
            //1.3 获取锁
            lock.lock();
            //select price from sku_info where id = 29;
            LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
            //设置查询条件
            queryWrapper.eq(SkuInfo::getId, skuId);
            //设置查询字段
            queryWrapper.select(SkuInfo::getPrice);
            SkuInfo skuInfo = skuInfoMapper.selectOne(queryWrapper);
            if (skuInfo != null) {
                return skuInfo.getPrice();
            }
            return new BigDecimal("0");
        } finally {
            //1.4 释放锁
            lock.unlock();
        }
    }

    /**
     * 根据SKUID查询当前SKU商品中平台属性以及属性值
     *
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = "attrList:")
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        //调用持久层动态SQL封装平台属性名称以及属性值
        List<BaseAttrInfo> list = baseAttrInfoMapper.selectAttrList(skuId);
        return list;
    }

    /**
     * 根据SPU_ID,SKU_ID查询所有销售属性，标识SKU选中销售属性
     *
     * @param skuId
     * @param spuId
     * @return
     */
    @Override
    @GmallCache(prefix = "spuSaleAttrListCheckBySku:")
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        //调用spu销售属性名称持久层接口完成动态SQL调用
        List<SpuSaleAttr> list = spuSaleAttrMapper.getSpuSaleAttrListCheckBySku(skuId, spuId);
        return list;
    }

    /**
     * 返回所有销售属性对应skuID map集合
     *
     * @param spuId
     * @return
     */
    @Override
    @GmallCache(prefix = "skuValueIdsMap:")
    public Map getSkuValueIdsMap(Long spuId) {
        Map result = new HashMap<>();
        //每一组销售属性对应skuId 集合
        List<Map> list = skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
        //遍历集合
        for (Map map : list) {
            Object value_ids = map.get("value_ids");
            Object sku_id = map.get("sku_id");
            result.put(value_ids, sku_id);
        }
        return result;
    }

}
